import axios from "axios"
import QS from "qs"
import errorCode from "./config"
import type { AxiosInstance, InternalAxiosRequestConfig } from "axios"
import type { Interceptors, IRewriteCreateAxiosDefaults, IRewriteAxiosRequestConfig } from "./@type"
import type { LoadingInstance, LoadingOptions } from "@/utils"
import {
  getRefreshToken,
  loading,
  refreshTokenApi,
  formatToken,
  getAccessToken,
  removeToken,
  bxMessageBox
} from "@/utils"
import { resetRouter } from "@/router"

/**
 * 1.统一loading加载，loading的有效 每个请求生效  > 所有实例生效, 默认在所有的实例开启loading加载
 * 2.统一防止单次进行多次请求（特殊需求可以单独控制，开启单次多次请求）
 */
export default class Request {
  instance: AxiosInstance // axios实例
  interceptors?: Interceptors // 拦截器
  isLoading: boolean //标记是否需要开启加载
  defaultLoading: boolean
  loadingInstance?: LoadingInstance // loading实例
  loadingOptions: LoadingOptions // loading配置

  whiteList: string[] // 白名单，无需携带token请求的路由
  ignoreMsgs: string[] // 忽略消息
  isRefreshToken: boolean // 是否已开启了无感token刷新
  requestQueue: (() => void)[] //401请求回调队列

  constructor(config: IRewriteCreateAxiosDefaults) {
    this.instance = axios.create(config) // 实例化axios实例

    this.interceptors = config.interceptors // 实例的拦截器回调
    this.defaultLoading = true
    this.isLoading = config.isShowLoading ?? true // 实例是否开启loading
    this.loadingOptions = config.loadingOptions ?? {} // 实例的loading配置

    this.whiteList = ["/login", "/refresh-token"]
    this.ignoreMsgs = ["无效的刷新令牌", "刷新令牌已过期"]
    this.isRefreshToken = false
    this.requestQueue = []

    // 全部实例默认开启loading、携带token、检查白名单、参数处理(如果参数传递的是''，把参数抹平清除)
    this.instance.interceptors.request.use(
      (config) => {
        /** 是否开启loading */
        if (this.isLoading) {
          this.loadingInstance = loading(this.loadingOptions)
        }
        /** 是否需要携带token ? */
        let isToken = !Boolean((config.headers || {}).isToken)
        this.whiteList.some((v) => {
          return (isToken = !(config.url && config.url.includes(v)))
        })
        if (getAccessToken() && isToken) {
          config.headers["Authorization"] = formatToken()
        }
        /** 参数处理 */
        const params = config.params || {}
        const data = config.data || false

        const postFlag =
          config.method?.toUpperCase() === "POST" &&
          config.headers["Content-Type"] === "application/x-www-form-urlencoded"
        const getFlag = config.method?.toUpperCase() === "GET" && params

        if (postFlag) config.data = QS.stringify(data)
        if (getFlag) {
          config.params = {}
          const paramsStr = QS.stringify(params, { allowDots: true })
          if (paramsStr) config.url = `${config.url}?${paramsStr}`
        }

        return config
      },
      (error) => {
        if (this.isLoading) {
          this.loadingInstance?.close()
        }
        return Promise.reject(error)
      }
    )
    this.instance.interceptors.response.use(
      async (response) => {
        if (this.isLoading) {
          this.loadingInstance?.close()
        }

        let { data, config } = response

        if (!data) {
          throw new Error()
        }

        // 未设置状态码则默认成功状态
        // 二进制数据则直接返回，例如说 Excel 导出
        if (["blob", "arraybuffer"].includes(response.request.responseType)) {
          // 注意：如果导出的响应为 json，说明可能失败了，不直接返回进行下载
          if (data.type !== "application/json") {
            return response.data
          }
          data = await new Response(data).json()
        }
        const code = data.code || 200
        const msg = data.msg || errorCode[code] || errorCode["default"]
        if (this.ignoreMsgs.includes(msg)) {
          // 如果是忽略的错误码，直接返回 msg 异常
          return Promise.reject(msg)
        } else if (code === 401) {
          this.refreshToken(config)
        } else if ([500, 901].includes(code)) {
          return Promise.reject(new Error(msg))
        } else if (code !== 200) {
          return Promise.reject("error")
        } else {
          return data
        }
        return config
      },
      (error) => {
        if (this.isLoading) {
          this.loadingInstance?.close()
        }
        return error
      }
    )

    // 当前实例开启的拦截器（如有需求，可以适当考虑在这为某个实例开启特定的拦截器）
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptor,
      this.interceptors?.requestInterceptorCatch
    )
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptor,
      this.interceptors?.responseInterceptorCatch
    )
  }

  // 每个请求都可以有自己的拦截，也可以在每个请求开启自定义的loading, 如果开启请求loading，全局的loading失效
  request<T>(config: IRewriteAxiosRequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      if (config.isCustomLoading) {
        this.isLoading = !config.isCustomLoading
        this.loadingInstance = loading(config.loadingOptions)
      }

      if (config.interceptors?.requestInterceptor) {
        config = config.interceptors.requestInterceptor(config)
      }

      this.instance
        .request(config)
        .then((res) => {
          if (config.interceptors?.responseInterceptor) {
            res = config.interceptors.responseInterceptor(res)
          }
          resolve(res as unknown as T)
        })
        .catch((error) => reject(error))
        .finally(() => {
          if (config.isCustomLoading) {
            this.loadingInstance?.close()
            this.isLoading = this.defaultLoading
          }
        })
    })
  }

  /** 401 无感刷新token处理 */
  async refreshToken(config: InternalAxiosRequestConfig<any>) {
    // 如果未认证，并且未进行刷新令牌，说明可能是访问令牌过期了
    if (!this.isRefreshToken) {
      this.isRefreshToken = true
      // 1. 刷新令牌需要携带旧RefreshToke去续期token，如果获取不到RefreshToke，只能执行登出操作
      if (!getRefreshToken()) {
        return this.handleAuthorized()
      }
      // 2. 进行刷新访问令牌
      try {
        await refreshTokenApi()
        // 2.1 刷新成功，则回放队列的请求 + 当前请求
        config.headers!.Authorization = formatToken()
        this.instance.request(config)
        this.requestQueue.forEach((cb) => cb())
      } catch (e) {
        // 当无感刷新token抛异常后，只能登录页面进行登录操作
        this.handleAuthorized()
      } finally {
        this.requestQueue = []
        this.isRefreshToken = false
      }
    } else {
      // 当并发请求时，从第二个的请求开始就把他放在回调队列里面，等待无感刷新token后再拿出来请求, 返回padding状态，使得后面可以获取到数据
      return new Promise((resolve) => {
        this.requestQueue.push(() => {
          config.headers.Authorization = formatToken()
          resolve(this.instance.request(config))
        })
      })
    }
  }

  /** 返回登录页面登录 */
  async handleAuthorized() {
    await bxMessageBox(
      "登录超时，请重新登录",
      {
        showCancelButton: false,
        closeOnClickModal: false,
        showClose: false,
        confirmButtonClass: "确定",
        type: "warning"
      },
      "confirm"
    )

    resetRouter() // 重置静态路由表
    removeToken()
  }

  get<T>(config: IRewriteAxiosRequestConfig): Promise<T> {
    if (config.contentType) config.headers["Content-Type"] = config.contentType
    return this.request<T>({
      ...config,
      method: "GET"
    })
  }

  post<T>(config: IRewriteAxiosRequestConfig): Promise<T> {
    if (config.contentType) config.headers["Content-Type"] = config.contentType
    return this.request<T>({ ...config, method: "POST" })
  }

  delete<T>(config: IRewriteAxiosRequestConfig): Promise<T> {
    if (config.contentType) config.headers["Content-Type"] = config.contentType
    return this.request<T>({ ...config, method: "DELETE" })
  }

  put<T>(config: IRewriteAxiosRequestConfig): Promise<T> {
    if (config.contentType) config.headers["Content-Type"] = config.contentType
    return this.request<T>({ ...config, method: "PUT" })
  }

  download<T>(config: IRewriteAxiosRequestConfig): Promise<T> {
    if (config.contentType) config.headers["Content-Type"] = config.contentType
    return this.request<T>({ ...config, method: "GET", responseType: "blob" })
  }

  upload<T>(config: IRewriteAxiosRequestConfig): Promise<T> {
    if (config.contentType) config.headers["Content-Type"] = config.contentType
    return this.request<T>({ ...config, method: "POST" })
  }
}
